#include <iostream>
#include <assert.h>
#include <unordered_map>

#include "Puzzle.hpp"

Puzzle::Puzzle() :
    _puzzle(0),
    _size(0)
{

}

Puzzle::Puzzle(Puzzle const & p) :
    _puzzle(0),
    _size(p._size)
{
    this->create(sqrt(p._size));
    this->copy(p);
}

Puzzle::Puzzle(unsigned int size, bool solved) :
    _puzzle(0),
    _size(0)
{
    this->create(size, solved);
}

Puzzle::~Puzzle()
{
    this->_deletePuzzle();
}

void Puzzle::_deletePuzzle()
{
    if (this->_puzzle != 0)
    {
        delete this->_puzzle;
        this->_puzzle = 0;
        this->_size = 0;
    }
}

Puzzle& Puzzle::operator=(Puzzle const & p)
{
    if (this != &p)
    {
        this->_size = p._size;
        this->create(sqrt(p._size));
        this->copy(p);
    }
    return *this;
}

bool Puzzle::operator==(Puzzle const & p) const
{
    if (this->_size == p.getSize())
    {
        for (int i = 0; i < this->_size; i++)
            if (this->_puzzle[i] != p._puzzle[i])
                return false;
        return true;
    }
    return false;
}

bool Puzzle::copy(Puzzle const & p)
{
    if (this->_puzzle != 0 && this->_size == p.getSize())
    {
        for (int i = 0; i < this->_size; i++)
            this->_puzzle[i] = p._puzzle[i];
        return true;
    }
    return false;
}

void Puzzle::create(unsigned int size, bool solved)
{
    this->_deletePuzzle();
    this->_size = size * size;
    this->_puzzle = new char[this->_size];
    for (int i = 0; i < this->_size; i++)
        this->_puzzle[i] = -1;
    if (solved)
        this->solve();
}

void Puzzle::display() const
{
    std::cout << *this;
}

int Puzzle::getSize() const
{
    return this->_size;
}

int Puzzle::getAt(int i) const
{
    if (i < this->_size && i >= 0)
        return this->_puzzle[i];
    return -1;
}

int Puzzle::getAt(unsigned int x, unsigned int y) const
{
    int i = x + y * (sqrt(this->_size));
    if (x < sqrt(this->_size) && y < sqrt(this->_size))
        return this->getAt(i);
    return -1;
}

int Puzzle::getAt(Coord const & c) const
{
    return this->getAt(c.x, c.y);
}

void Puzzle::setAt(int x, int y, int value)
{
    int i = x + y * sqrt(this->_size);
    if (i < this->_size && i >= 0)
        this->_puzzle[i] = value;
}

void Puzzle::setAt(Coord const & c, int value)
{
    this->setAt(c.x, c.y, value);
}

void Puzzle::solve()
{
    if (this->_size > 0)
    {
        this->_resetPuzzle();
        int i = 1;
        int max = this->_size;
        Coord c(0, 0);
        int dirx = 1;
        int diry = 0;
        while (i < max)
        {
            this->setAt(c, i);
            if (!this->_isEmptyNextCell(c, dirx, diry))
                this->_turnRight(dirx, diry);
            c.x += dirx;
            c.y += diry;
            i++;
        }
        this->setAt(c, 0);
    }
}

bool Puzzle::_isEmptyNextCell(Coord const & current, int dirx, int diry)
{
    int nextx = current.x + dirx;
    int nexty = current.y + diry;
    int sqrtSize = sqrt(this->_size);
    if (nextx >= 0 && nextx < sqrtSize && nexty >= 0 && nexty < sqrtSize
        && this->getAt(current.x + dirx, current.y + diry) == -1)
        return true;
    return false;
}

void Puzzle::_resetPuzzle(int value)
{
    for (int i = 0; i < this->_size; i++)
        this->_puzzle[i] = value;
}

void Puzzle::_turnRight(int &dirx, int &diry)
{
    if (dirx == 1 && diry == 0) {
        dirx = 0;
        diry = 1;
    } else if (dirx == 0 && diry == 1) {
        dirx = -1;
        diry = 0;
    } else if (dirx == -1 && diry == 0) {
        dirx = 0;
        diry = -1;
    } else if (dirx == 0 && diry == -1) {
        dirx = 1;
        diry = 0;
    }
}

bool Puzzle::move(int direction)
{
    int dirx = 0;
    int diry = 0;
    switch (direction)
    {
    case UP:
        diry = -1;
        break;
    case RIGHT:
        dirx = 1;
        break;
    case DOWN:
        diry = 1;
        break;
    case LEFT:
        dirx = -1;
        break;
    }
    Coord voidCoord;
    if (this->_getVoidCoord(voidCoord))
    {
        Coord target(voidCoord);
        target.x += dirx;
        target.y += diry;
        char tmp = this->getAt(target);
        if (tmp != -1)
        {
            this->setAt(target, 0);
            this->setAt(voidCoord, tmp);
            return true;
        }
    }
    return false;
}

bool Puzzle::_getVoidCoord(Coord& c) const
{
    for (int i = 0; i < this->_size; i++)
    {
        if (this->_puzzle[i] == 0)
        {
            int size = sqrt(this->_size);
            c.x = i % size;
            c.y = i / size;
            return true;
        }
    }
    return false;
}
